魔鬼隐藏在细节之中
--我看Intel CT ADE
我设计koodoo语言的动因当然是解决实际问题,在几年前我面临着复杂多变的IVR应用,复杂多变的流程、不同的后台数据库,甚至牵涉到各种运算。于是试图设计一种全新的脚本语言,设想CTI的大部分工作均由这个单一的语言来完成。设计时不由自主的受到各种流行语言的影响,比如C/C++, JAVA, 当然还有同样是脚本语言的Python和AWK。可能是自己的孤陋寡闻,当时对Intel CT ADE并不了解,直到最近好几个朋友告诉我,说你的平台和Intel CT ADE很相似,于是认真地坐下来,审视并研究这个工业巨头的软件平台产品,并试图写下一点个人的看法。
当然我写这篇东西,里面的观点很多朋友肯定不能同意,我自己在动笔之前也很犹豫,毕竟作为一种语言的设计者,对自己的东西会有所偏爱,拿自己的优点去比较对方的缺点,在立场上很难做到公正。但反过来看,正因为我是语言的设计者,也许我能看到对方在设计上的某些历史痕迹,看到他的取舍。
也许CTI的市场太小,市场上并没有中立的权威的机构去做这些评测性的工作,这是令人遗憾的。
下面的评论主要针对Intel的VOS语言(也叫AD语言),其中文文档来自: http://www.ctiforum.com/train/intel/tech/tech01_001_00.htm
一、语法结构
据介绍:
"AD语言是一种高效率的类似于C或C++的电话专用脚本语言。一个只有14行AD语言代码的应用(或三个流程图标)可能需要500多行的C语言或者C++的代码。特别为Intel的Dialogic硬件和电话开发而设计的 AD语言编译成P代码。它允许所有可用的硬件端口运行在单一的线程上(Windows 2000)。它具有特别的高性能,因为它相对于大多数多线程的C或C++应用降低了管理费用而性能最优。"
VOS语言试图以C/C++的语法为蓝本,这当然是正确的选择,问题是VOS语言自行其是的语法和C/C++相距万里:
C语法:
for(..) { ... } while(...) { ... }
VOS语法:
for(..) ... endfor while(..) ... endwhile if(...) ... endif
凭空多出这许多无用的关键字。
语句注释,VOS语言只有单行注释,而且使用单一分隔字符'#'号--C/C++语言是'//', 没有多行注释对于屏蔽大段代码简直太辛苦了。
其实VOS语言如此设计当然是为了简化编译系统, 'endif'比'}'更容易和'if'匹配, 单个字符的'#'比两个字符的'//'更容易处理,何况'/'还是除号运算符。编译系统是简单了,但用户感觉别扭。
VOS语言不支持switch(), case这种多分支开关语句的语法结构,在IVR的语音菜单中,开关语句非常有用。
也许VOS语言的设计者认为有if语句就够了,也许switch(), case这种多分支开关语句对VOS语言的编译系统来说,太复杂了。
二、运算符
1. 字符串拼接:
'&'号, 这个有点像早期的BASIC语言,但我认为不如大多数语言的'+'号来得直观。而且使用'+'号可以进行更复杂的运算。在VOS语言字符串拼接的优先级最高,仿佛说“先加减后乘除”,不符合大多数人的习惯。
2. 逻辑运算符号
C/C++采用直观的符号: ==, !=, &&, ||, <, >
VOS语言则采用关键字: eq, not, and, or; 但不相等比较还是用符号:<>
更为别扭的是,数值比较和字符串比较居然使用不同的符号:
- 比较数值相等: eq
- 比较字符串相等: streq
- 比较字符串不相等: strneq
太不简明了, 而且缺少内部的一致性。
三、变量和数组
变量和数组在VOS语言中有很大的限制,不利于开发更大、更复杂的应用。
1. 没有局部变量只有全局变量
VOS语言所有的变量必须在程序的开始定义:
dec var v1... ... enddec
大量使用全局变量的弊端大家都知道,每个全局变量相当于一个接口,在函数内部没有局部变量,等于增大了模块的藕合度。
对于大型项目而言,高藕合度的模块代码相当难以维护。这点在现代的软件工程理论中已经是定论。
实际上现代的脚本语言如Python, 变量不需要先定义,需要时赋值、使用。
2. 变量在内部表示为字符串,不直接支持浮点运算
变量在内部表示为字符串大概是借用Unix下的脚本语言AWK语言的设计,尤其方便和外部动态库的接口,因为这样一来,外部DLL的函数只需要处理单一的C语言数据类型: char *.
但是,在VOS语言里变量有长度的限制,而且必须事先指定:
dec var x : 2; enddec
对于这个限制,他们解释道:
- "为什么要对变量限定最大长度?
- 为什么VOS不运行动态分配内存?
- 主要是因为呼叫处理系统常常在无人维护的状态下运行数天、数星期或者数个月。
- 设计VOS时避免使用内存动态分配是为了防止内存碎片和内存不足等问题,这也是为VOS设计一种新的语言而不使用现存的语言的原因。"
对于这个解释,我不能认同,现代的操作系统已经不是当年简陋的带有640K内存限制的DOS,已经不是"昔日吴下阿蒙", 现在我们使用的是带有大容量内存的快速机器,Windows2000、Unix等现代操作系统也有着高效稳定的内存调度算法。
如果真像他们所说的,那像C++这种频繁分配、释放内存的语言,甚至JAVA、C#等这些带垃圾回收的语言,岂不是根本没有办法使用到关键系统之中去?
实际上很多电信级的应用都是用C/C++写成,而且大量地使用动态内存分配。采用JAVA语言的J2EE更是企业级运算的标准。
在VOS语言的内部不直接支持浮点运算,这是个问题,毕竟浮点运算还是很常见的。当然问题的根源在于其变量在内部都表示为字符串,VOS的编译器很难处理。
还好,作为弥补,CT ADE提供了外部库,可以进行浮点运算,但这样就不是很直观了,因为一个简单运算都要变成函数调用,如加法要调用fp_add函数, 减法要调用fp_sub等等。
3. 数组
VOS语言中数组这样定义:
dec var Arr[1..10] : 8; enddec
必须指定下标和长度,而且下标最多只能在0..255的范围内,一个程序中定义的数组个数也不能超过255。
这么多的限制,必然影响数组的使用。
而且从库函数来看,没有充分利用数组的灵活性,在这里自夸一下,说说Koodoo语言的数组。
Koodoo语言的数组当然没有上面那些限制,它甚至不需要定义,下标也不会越界,而且在库函数一级有神奇的功能:
m = 0; AnlyStr("U.S.A., CN, JAP, HK", ",", m); // 分解串到数组 m自动变成一个数组,这个数组有4个成员: m[0] - "U.S.A." m[1] - "CN" m[2] - "JAP" m[3] - "HK"
当然还有读取文本文件的每一行到数组,等等。
四、函数
VOS语言的函数定义,使用两个关键字'func'和'endfunc', 比如:
func add(arg1,arg2) return arg1+arg2; endfunc
VOS语言函数的最大问题是没有局部变量,这个在前面变量部分已经讨论过,此外,对于多参数返回,VOS语言没有提供引用传递参数的办法,也许设计者认为既然变量都是全局的,所有的变量都能穿透到函数,也就不需要引用传递了。
五、库函数印象
VOS语言的库函数在信令接续、语音控制等方面很细也很全面,几乎所有的Dialogic的API函数都有对应的VOS库函数,这对于电信级别的应用开发非常方便。但是有利也有弊,对于一般应用则嫌之烦琐和复杂,换一种说法就是“封装的太薄”。
其它方面的库函数,可以说乏善可陈,这也许和它的数据类型不丰富以及语言机制有关系,也和它的封装风格有关系。
当然,因为它允许外部动态库,而外部库基本上可以不受VOS语言的限制,所以有一些功能较为强大的外部函数可供使用。
六、异曲同工的设计
话说回来,VOS语言的有些设计我还是比较认同,因为Koodoo语言也有相同的设计,也是很多朋友认为VOS语言与Koodoo语言比较相似的原因。
归纳起来, 有:
1. 脚本在单一的线程上运行,编程模式简单
2. 常量符号定义, 都是使用const关键字
3. 都支持文件包含, 使用#include语句
4. 支持外部动态库调用(外部函数),构成3种函数调用的方式:
- 1). 系统内部函数
- 2). 用户自定义函数
- 3). 外部动态库函数
不过,两者在细节上还是有差别的,比如对于外部动态库,Koodoo语言提供了装载语句,可以任何时候装载动态库; 而VOS在配置文件中说明,在程序开始时候装载。
对于用户自定义函数,两者都可以递归调用。
5. 支持任务(线路)间通讯
VOS语言支持: 信号量、消息和全局变量
Koodoo语言支持: 消息队列和全局变量
因为两者对任务的概念有所不同,所以在细节上也不一样。
6. 支持挂断事件处理块
onhangup # Hang-up processing TrunkDisconnect(), restart; endonhangup
Koodoo语言则提供了OnDisconn()系统函数。
更进一步,Koodoo语言还提供了OnSysQuit()系统函数, 响应系统退出事件.
本文主要从脚本编程语言上指出VOS语言的不足,其它诸如编程、发布环境,系统架构等其它方面未作涉及。
总体上看来,Intel CT ADE的VOS是有着DOS时代遗迹的“古老”语言,对于构造大型的IVR应用存在不小的限制。
bluesen 2004.7 于深圳